package com.unswift.cloud.aop;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;

import com.unswift.annotation.api.Api;
import com.unswift.annotation.api.ApiField;
import com.unswift.annotation.api.ApiMethod;
import com.unswift.cloud.annotation.request.Active;
import com.unswift.cloud.config.RequestConfig;
import com.unswift.cloud.pojo.dto.BaseDto;
import com.unswift.cloud.pojo.vo.ResponseBody;
import com.unswift.cloud.utils.ServletUtils;
import com.unswift.utils.ExceptionUtils;
import com.unswift.utils.ObjectUtils;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

@Aspect
@Component
@Api(value="控制器限制AOP，请求方式必须是POST同时，controller层入参必须是Dto，出参必须是Vo", author = "unswift", date = "2023-09-05", version = "1.0.0")
public class RequestAop {

	
	@Autowired
	@ApiField("请求yml配置")
	private RequestConfig requestConfig;
	
	@ApiMethod(value="DeleteMapping注解切面")
	@Pointcut("@annotation(org.springframework.web.bind.annotation.DeleteMapping) && !execution(public * com.unswift.cloud.feign.*.*(..))")
	public void entryDeleteMappingPoint() {
	}
	
	@Around("entryDeleteMappingPoint() && @annotation(deleteMapping)")
	@ApiMethod(value="DeleteMapping注解切面执行方法", params = {@ApiField("切面需要执行的方法封装"), @ApiField("DeleteMapping被切面对象")})
	public Object around(ProceedingJoinPoint point, DeleteMapping deleteMapping) throws Throwable {
		return handleNotPostRequest(point);
	}
	
	@ApiMethod(value="GetMapping注解切面")
	@Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping) && !execution(public * com.unswift.cloud.feign.*.*(..))")
	public void entryGetMappingPoint() {
	}
	
	@Around("entryGetMappingPoint() && @annotation(getMapping)")
	@ApiMethod(value="GetMapping注解切面执行方法", params = {@ApiField("切面需要执行的方法封装"), @ApiField("GetMapping被切面对象")})
	public Object around(ProceedingJoinPoint point, GetMapping getMapping) throws Throwable {
		return handleNotPostRequest(point);
	}
	
	@ApiMethod(value="PatchMapping注解切面")
	@Pointcut("@annotation(org.springframework.web.bind.annotation.PatchMapping) && !execution(public * com.unswift.cloud.feign.*.*(..))")
	public void entryPatchMappingPoint() {
	}
	
	@Around("entryPatchMappingPoint() && @annotation(patchMapping)")
	@ApiMethod(value="PatchMapping注解切面执行方法", params = {@ApiField("切面需要执行的方法封装"), @ApiField("PatchMapping被切面对象")})
	public Object around(ProceedingJoinPoint point, PatchMapping patchMapping) throws Throwable {
		return handleNotPostRequest(point);
	}
	
	@ApiMethod(value="PostMapping注解切面")
	@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping) && !execution(public * com.unswift.cloud.feign.*.*(..))")
	public void entryPostMappingPoint() {
	}
	
	@Around("entryPostMappingPoint() && @annotation(postMapping)")
	@ApiMethod(value="PostMapping注解切面执行方法", params = {@ApiField("切面需要执行的方法封装"), @ApiField("PostMapping被切面对象")})
	public Object around(ProceedingJoinPoint point, PostMapping postMapping) throws Throwable {
		String path=ServletUtils.getRequest().getServletPath();
		this.validateActive(point, path);
		if(requestConfig.matchRequestParamsRuleWhite(path)) {
			return point.proceed();
		}
		return handlePostRequest(point, path);
	}
	
	@ApiMethod(value="PutMapping注解切面")
	@Pointcut("@annotation(org.springframework.web.bind.annotation.PutMapping) && !execution(public * com.unswift.cloud.feign.*.*(..))")
	public void entryPutMappingPoint() {
	}

	@Around("entryPutMappingPoint() && @annotation(putMapping)")
	@ApiMethod(value="PutMapping注解切面执行方法", params = {@ApiField("切面需要执行的方法封装"), @ApiField("PutMapping被切面对象")})
	public Object around(ProceedingJoinPoint point, PutMapping putMapping) throws Throwable {
		return handleNotPostRequest(point);
	}
	
	@ApiMethod(value="RequestMapping注解切面")
	@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping) && !execution(public * com.unswift.cloud.feign.*.*(..))")
	public void entryRequestMappingPoint() {
	}

	@Around("entryRequestMappingPoint() && @annotation(requestMapping)")
	@ApiMethod(value="RequestMapping注解切面执行方法", params = {@ApiField("切面需要执行的方法封装"), @ApiField("RequestMapping被切面对象")})
	public Object around(ProceedingJoinPoint point, RequestMapping requestMapping) throws Throwable {
		String path=ServletUtils.getRequest().getServletPath();
		System.out.println("controller:"+path);
		this.validateActive(point, path);
		RequestMethod[] methods = requestMapping.method();
		if(ObjectUtils.isNotEmpty(methods)) {
			if(requestConfig.matchNotPostUrl(path)) {
				return point.proceed();
			}
			if(requestConfig.matchRequestParamsRuleWhite(path)) {
				return point.proceed();
			}
			for (RequestMethod method : methods) {
				if(!RequestMethod.POST.equals(method)) {
					throw ExceptionUtils.message("request.must.be.a.post", path);
				}
			}
			return handlePostRequest(point, path);
		}else {
			throw ExceptionUtils.message("request.must.be.a.post", path);
		}
	}
	
	@ApiMethod(value="处理不是Post请求的切面方法", params = @ApiField("切面封装对象"), returns = @ApiField("切面方法执行结果"))
	private Object handleNotPostRequest(ProceedingJoinPoint point) throws Throwable  {
		String path=ServletUtils.getRequest().getServletPath();
		this.validateActive(point, path);
		if(requestConfig.matchNotPostUrl(path)) {
			return point.proceed();
		}
		throw ExceptionUtils.message("request.must.be.a.post", path);
	}
	
	@ApiMethod(value="处理Post请求的切面方法", params = {@ApiField("切面封装对象"), @ApiField("请求路径")}, returns = @ApiField("切面方法执行结果"))
	private Object handlePostRequest(ProceedingJoinPoint point, String path) throws Throwable  {
		Object[] params=point.getArgs();
		if(ObjectUtils.isNotEmpty(params)) {
			int index=0;
			for (Object param : params) {
				index++;
				if(ObjectUtils.isEmpty(param)) {//参数为空，不考虑
					continue;
				}
				if(param instanceof HttpServletRequest 
						|| param instanceof HttpServletResponse 
						|| param instanceof HttpSession 
						|| param instanceof MultipartFile
						|| param instanceof MultipartFile[]) {
					continue;
				}
				if(!(param instanceof BaseDto)) {
					throw ExceptionUtils.message("request.parameters.must.inherit.basedto", path, index);
				}
			}
		}
		Object result=point.proceed();
		if(ObjectUtils.isNotEmpty(result)) {
			if(!(result instanceof ResponseBody)) {
				throw ExceptionUtils.message("request.return.parameter.must.be.a.responsebody.object", path);
			}
		}
		return result;
	}
	
	@ApiMethod(value="验证请求是否激活，只有激活的请求才能访问", params = {@ApiField("切面封装对象"), @ApiField("请求路径")})
	private void validateActive(ProceedingJoinPoint point, String path) {
		MethodSignature methodSignature = (MethodSignature) point.getSignature();
	    Method targetMethod = methodSignature.getMethod();
		ExceptionUtils.empty(targetMethod.getAnnotation(Active.class), "request.not.yet.activated", path);
	}
}
